昨天我們把落單的 UI 元件跟其模組關聯完之後,今天接著要來設定路由的部份!
開始設定路由前,可以先看看我們準備好的路徑定義檔 - app-path.const.ts :
export const appPath = {
  // 首頁
  home: '',
  // 甜點
  products: 'products',
  // 登入
  login: 'login',
  // 購物車
  cart: 'cart',
  // 結帳
  checkout: 'checkout',
  // 結帳流程
  checkoutFlow: {
    // 運送
    customerInfo: 'customer-info',
    // 付款
    paymentInfo: 'payment-info',
    // 發票
    receiptInfo: 'receipt-info'
  },
  // 結帳成功
  success: 'success'
};
之所以會建立這個定義檔是因為不希望 Coding 的時候要在程式碼裡到處 Hard Code ,容易打錯字又不好維護。既然是會重複用到的東西,我們就把它寫成定義檔,以後要維護或是要調整的時候也會比較好處理。
雖然這個專案應該是不會需要被維護,但好的習慣很重要!
複習一下我們所規劃的路由:

按照我們原本所規劃的路由來設定的話,應該會長這樣:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Constant
import { appPath } from './app-path.const';
// for Angular 7 以下
const routes: Routes = [
  {
    path: appPath.home,
    loadChildren: './home/home.module#HomeModule'
  },
  {
    path: appPath.products,
    loadChildren: './product-section/product-section.module#ProductSectionModule'
  },
  {
    path: appPath.login,
    loadChildren: './login/login.module#LoginModule'
  },
  {
    path: appPath.cart,
    loadChildren: './cart/cart.module#CartModule'
  },
  {
    path: appPath.checkout,
    loadChildren: './checkout/checkout.module#CheckoutModule'
  },
  {
    path: appPath.success,
    loadChildren: './success/success.module#SuccessModule'
  },
  {
    path: '**',
    redirectTo: appPath.home,
    pathMatch: 'full'
  }
];
// for Angular 8 以上
const routes: Routes = [
  {
    path: appPath.home,
    loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
  },
  {
    path: appPath.products,
    loadChildren: () => import('./product-section/product-section.module').then(m => m.ProductSectionModule)
  },
  {
    path: appPath.login,
    loadChildren: () => import('./login/login.module').then(m => m.LoginModule)
  },
  {
    path: appPath.cart,
    loadChildren: () => import('./cart/cart.module').then(m => m.CartModule)
  },
  {
    path: appPath.checkout,
    loadChildren: () => import('./checkout/checkout.module').then(m => m.CheckoutModule)
  },
  {
    path: appPath.success,
    loadChildren: () => import('./success/success.module').then(m => m.SuccessModule)
  },
  {
    path: '**',
    redirectTo: appPath.home,
    pathMatch: 'full'
  }
];
@NgModule({
  imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: PreloadAllModules
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }
用路徑定義檔來設定路由是不是清爽多啦?!未來就算路徑要變動,也只要修改定義檔裡的定義就好,不需要到處去尋找還有哪個地方沒有改到。
記得加上預先載入的設定,避免檔案變大之後,初次換頁時會有頓頓的感覺。
另外我們這次使用預設的 PathLocationStrategy 路由策略,注意網址後面不要有
#噢!
然後我們再到各模組裡處理自己的路由:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
const routes: Routes = [
  {
    path: '',
    component: HomeComponent
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class HomeRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProductSectionComponent } from './product-section.component';
import { ProductListComponent } from './product-list/product-list.component';
const routes: Routes = [
  {
    path: '',
    component: ProductSectionComponent,
    children: [
      {
        path: ':type',
        component: ProductListComponent
      }
    ]
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductSectionRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login.component';
const routes: Routes = [
  {
    path: '',
    component: LoginComponent
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class LoginRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CartComponent } from './cart.component';
const routes: Routes = [
  {
    path: '',
    component: CartComponent
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CartRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SuccessComponent } from './success.component';
const routes: Routes = [
  {
    path: '',
    component: SuccessComponent
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class SuccessRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// Constant
import { appPath } from '../app-path.const';
// Component
import { CheckoutComponent } from './checkout.component';
import { CustomerInfoComponent } from './customer-info/customer-info.component';
import { PaymentInfoComponent } from './payment-info/payment-info.component';
import { ReceiptInfoComponent } from './receipt-info/receipt-info.component';
const routes: Routes = [
  {
    path: '',
    component: CheckoutComponent,
    children: [
      {
        path: '',
        redirectTo: appPath.checkoutFlow.customerInfo,
        pathMatch: 'full'
      },
      {
        path: appPath.checkoutFlow.customerInfo,
        component: CustomerInfoComponent
      },
      {
        path: appPath.checkoutFlow.paymentInfo,
        component: PaymentInfoComponent
      },
      {
        path: appPath.checkoutFlow.receiptInfo,
        component: ReceiptInfoComponent
      },
      {
        path: '**',
        redirectTo: appPath.checkoutFlow.customerInfo,
        pathMatch: 'full'
      }
    ]
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CheckoutRoutingModule { }
如此一來我們應該已經大致上將所有路由都設定完了,趕快來驗證一下我們設的路由有沒有問題。
首先我們先打開 app.component.ts 檔,將裡面的程式碼改成這樣:
import { Component } from '@angular/core';
// Constant
import { appPath } from './app-path.const';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  /**
   * 給 Template 用的路由定義
   *
   * @memberof AppComponent
   */
  path = appPath;
}
然後再將 app.component.html 裡的 Template 改成這樣:
<ul>
  <li><a [routerLink]="path.home">Home</a></li>
  <li><a [routerLink]="path.login">Login</a></li>
  <li><a [routerLink]="[path.products, 'all']">Products</a></li> 
  <li><a [routerLink]="path.cart">Cart</a></li>
  <li><a [routerLink]="path.checkout">Checkout</a></li>
  <li><a [routerLink]="path.success">Success</a></li>
</ul>
<router-outlet></router-outlet>
最後再到 product-section.component.html 跟 checkout.component.html 裡加入路由插座 <router-outlet></router-outlet> :
<p>
  product-section works!
</p>
<router-outlet></router-outlet>
<p>
  checkout works!
</p>
<router-outlet></router-outlet>
設定完成!!
如果我們的設定沒有任何問題的話,應該可以看到像這樣子的效果:

除了都能正常導頁之外,有注意到網址沒有 # 了嗎?!這是因為這次我們採用預設的 PathLocationStrategy 路由策略的關係。
所以如果設定完之後但沒有看到畫面時先別緊張,檢查一下你的 URL 是不是含有 # 。
如果有,拿掉之後再看看有沒有恢復正常;如果沒有,趕快留言告訴我你的問題吧!!
關於路由的設定就先到這邊,接下來要開始套版囉!!
我們明天見!!
優質好文^^
文件app-path.const.ts
// 結帳成功
success: 'suceess' 打錯一個字母喔
很高興你喜歡我的文章,也謝謝你特別留言告知!!
已修正囉!!

Leo大大 小弟卡關需要請教
Q1 無法獲取路徑
Q2 app-path.const 中 Object有紅線


Hi Jackson,
抱歉這麼晚才回你,因為我前幾天人不在台灣^^"
關於你的問題:
Q1 無法獲取路徑
這個部分我可能要看你的 RouteModule 以及 HTML 、 TS 檔才會比較清楚你的問題是出在哪裡。
Q2 app-path.const 中 Object有紅線
這個是因為 TypeScript 不認得 Object 這個型別。
比較簡單的解決辦法是直接拿掉 Object.freeze() ,留下 {} 及其裡面的資料就好,如:
export const appPath = {
  // 首頁
  home: '',
  // 甜點
  products: 'products',
  
  // ...
};
因為這個 Object.freeze() 其實是我個人加上去,目的是為了避免 appPatch 這個 contant(常數)裡的值被誤改。
另外一種方式則是在最上方加上:
declare Object: any;
這樣也可以。
重做兩次還是卡在這個位子


別慌,有看到問題點了~
第一個問題是,你的 app-routing.module.ts 檔裡的第四行 - routes 這個變數是空陣列,裡面沒有任何設定,是不是忘記設定囉?!XD
第二個問題,你就直接把 Object.freeze() 拿掉沒關係,留下 {} 區間裡的所有設定就好。
Problem fixed感謝大大 小弟不才誤會app-routing.module.ts那段不用加 
至於第二個問題我發現Object即使有紅字也沒影響頁面,我就先不管了XD 再次謝謝您的回答
很高興有幫上你的忙!

麻煩大家留意一下!
原本我在 app-path.const.ts 裡,有使用 Object.freeze() 這個函式把 {} 裡所有的設定包起來,目的是為了防止定義檔不小心被修改。
不過目前發現,這個方式使用在路由的定義上時,在使用 Production 模式去編譯或是運行時,Angular 的路由會拿不到我們事先定義好的值。
因此,我在文章中的 app-path.const.ts 裡,已經將 Object.freeze() 拿掉,也請大家在路徑的定義檔裡先移除 Object.freeze() 這個函式!!
非常抱歉造成大家的困擾,還請大家多多留意!!

他說找不道模組欸 請問這個怎麼解決
Hi, brad840628
看起來是路徑的問題,可能要留意一下該檔案的擺放位置唷!
請問要怎麼新增這個檔案 他是module 還是 component
Hi brad840628,
它就只是個單純的 .ts 檔,直接新增檔案然後用 .ts 結尾命名即可唷!
HI 大大你好
我在終端機執行了ng serve
畫面是顯示建置成功了
可是我點開localhost:4200卻什麼也沒有的一片空白
於是我點開F12查看了console
發現有一個錯
想問問這應該怎麼解決QQ
Hi a405066,
這個錯誤訊息是因為無窮迴圈的關係,原因是你在程式的某個地方有出現自己呼叫自己的函式造成的。
HI 大大你好
我後來有找到我自己呼叫自己的地方
謝謝回覆!
Hi a405066,
有找到就好! ^^
Hi Leo 大大,
很喜歡這篇文章, 可以跟著步驟一步步學習如何建立網站
剛才發現這篇教學似乎因為延遲聲明的方法在Angular後面版本有變更, 會找不到模組
core.js:6140 ERROR Error: Uncaught (in promise): Error: Cannot find module './login/login.module'
Error: Cannot find module './login/login.module'
已找到方法解決, 在這裡補充說明~
原本:
{
path: appPath.login,
loadChildren: './login/login.module#LoginModule'
}
變成:
{
path: appPath.login,
loadChildren: () => import('./login/login.module').then(m => m.LoginModule)
}
Hi Chil,
很高興你喜歡它,
另外也非常感謝你的補充,
我再把它加上去 ^^
您好 Leo大大
最近在看您的文章學習angular
有些關於路由的問題想請教
這是我目前所想的架構圖為下
但因為想要新增一個進入頁面時URL為空的驗證頁
判斷是否有使用者權限再進入首頁所以修改架構圖為下
有做一個LayoutModule是因為下面3個子路由會共用到同一個FooterTab
想問一下修改後的架構有沒有問題
是否需要將LayoutModule加入路由
讓首頁的路徑調整成 'layout/home'
如有有錯誤理解請麻煩糾正我一下
Hi bsexp301479,
關於權限驗證,最好的檢查時機點是在路由守門員 Guard 裡噢
參考文章:https://ithelp.ithome.com.tw/articles/10208485
感謝您
現在是在路由的設定上有點錯亂
剛剛重新想了一下路由的邏輯有達到我想要的效果了~